package org.durandj.ystream.client.network;

import java.nio.ByteBuffer;
import java.security.InvalidParameterException;

/**
 * Similar to a {@link Message} but specific to network transfer
 * since it also keeps track of the target machine and the
 * source machine.
 * 
 * @author durandj
 * @version 1.0
 *
 */
public class Packet implements Comparable<Packet>
{
	/**
	 * Similar to message priority but used by the packet handling
	 * system to handle packets efficiently.
	 * 
	 * @author durandj
	 * @version 1.0
	 *
	 */
	public static enum Priority
	{
		HIGH,
		MEDIUM,
		LOW
	}
	
	/**
	 * The maximum amount of data that can be crammed into a packet.
	 */
	public static final int MAX_PACKET_DATA_SIZE = 512;
	
	/**
	 * The maximum size of a packet.
	 */
	public static final int MAX_PACKET_SIZE = 4 + 4 + 4 + MAX_PACKET_DATA_SIZE;
	
	/**
	 * The target machine.
	 */
	private String target;
	
	/**
	 * The source machine.
	 */
	private String source;
	
	/**
	 * The packet opcode.
	 */
	private int opcode;
	
	/**
	 * The packet priority.
	 */
	private Priority priority;
	
	/**
	 * The size of the contents buffer.
	 */
	private int size;
	
	/**
	 * The contents of the packet.
	 */
	private ByteBuffer contents;
	
	/**
	 * Creates a packet with the given traits.
	 * 
	 * @param priority - the priority of the packet
	 * @param opcode - the opcode of the packet
	 * @param target - the target machine
	 * @param source - the source machine
	 */
	public Packet(Priority priority, int opcode, String target, String source)
	{
		this(priority, opcode, target, source, null);
	}
	
	/**
	 * Creates a packet with the given traits.
	 * 
	 * @param priority - the priority of the packet
	 * @param opcode - the opcode of the packet
	 * @param target - the target machine
	 * @param source - the source machine
	 * @param contents - the contents of the packet
	 */
	public Packet(Priority priority, int opcode, String target, String source, ByteBuffer contents)
	{
		if(priority == null)
			throw new NullPointerException("Must give a valid priority.");
		
		if(opcode < 0)
			throw new InvalidParameterException("Must give a valid opcode.");
		
		if(target == null || target.isEmpty())
			throw new InvalidParameterException("Must give a valid target.");
		
		if(source == null || source.isEmpty())
			throw new InvalidParameterException("Must give a valid source.");
		
		this.priority = priority;
		this.opcode = opcode;
		this.target = target;
		this.source = source;
		this.contents = contents;
		
		// We are going to make all packets be the same size by filling in the extra
		// space in the contents with null bytes.
		if(this.contents == null)
		{
			this.size = 0;
			this.contents = ByteBuffer.allocate(Packet.MAX_PACKET_DATA_SIZE);
		}
		else if(this.contents.capacity() <= Packet.MAX_PACKET_DATA_SIZE)
		{
			this.size = contents.limit();
			this.contents = ByteBuffer.allocate(Packet.MAX_PACKET_DATA_SIZE);
			this.contents.put(contents);
		}
		// We're also going to make sure that the contents that were passed are not too big.
		else if(this.contents.capacity() > Packet.MAX_PACKET_DATA_SIZE)
		{
			this.size = Packet.MAX_PACKET_DATA_SIZE;
			this.contents = ByteBuffer.allocate(Packet.MAX_PACKET_DATA_SIZE);
			
			while(this.contents.hasRemaining())
				this.contents.put(contents.get());
		}
		
		this.contents.position((size == 0) ? 0:(this.size - 1));
		while(this.contents.hasRemaining())
			this.contents.put((byte)0);
		
		// We're going to rewind the contents to the beginning now.
		this.contents.rewind();
	}
	
	/**
	 * Gets the priority of the packet.
	 * 
	 * @return the priority
	 */
	public Priority getPriority()
	{
		return priority;
	}
	
	/**
	 * Gets the opcode of the packet.
	 * 
	 * @return the opcode
	 */
	public int getOpcode()
	{
		return opcode;
	}
	
	/**
	 * Gets the target of the packet.
	 * 
	 * @return the target
	 */
	public String getTarget()
	{
		return target;
	}
	
	/**
	 * Gets the source of the packet.
	 * 
	 * @return the source
	 */
	public String getSource()
	{
		return source;
	}
	
	/**
	 * Gets the size of the contents buffer.
	 * 
	 * @return the contents buffer size
	 */
	public int getSize()
	{
		return size;
	}
	
	/**
	 * Gets the contents of the packet.
	 * 
	 * @return the contents
	 */
	public ByteBuffer getContents()
	{
		return contents;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(Packet packet)
	{
		switch(priority)
		{
		case HIGH:
			if(packet.getPriority() == Priority.HIGH)
				return 0;
			else
				return 1;
			
		case MEDIUM:
			if(packet.getPriority() == Priority.HIGH)
				return -1;
			else if(packet.getPriority() == Priority.MEDIUM)
				return 0;
			else
				return 1;
			
		case LOW:
			if(packet.getPriority() == Priority.LOW)
				return 0;
			else
				return -1;
		}
		
		return 0; // This should never be reached!
	}
}
